/**
 * CS543 HW4 Joseph Meyer
 */
package edu.src.hardware;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.IOUtils;

import edu.src.logger.Logger;


/**
 * Another Singleton. Two in the same project seems wrong. Anyway,
 * The point of this is too simulate the physical backing store
 * that this program uses to access data. The private constructor
 * will read in the BACKING_STORE.bin file and create a list of 256
 * frames, each frame contains 256 bytes. 
 * <p>
 * When the <code>PageTable</code> accesses this class, it will provide
 * the backign store with a page number and offset (0-indexed). The
 * Backing store will respond with the byte that lives at that address.
 * <p>
 * Refer to the JUnits to verify functionality.
 * 
 * @author meyer
 */
public class BackingStore {

	private Logger logger;
	private static BackingStore instance;
	private byte[] backingBytes;
	private List<BackingFrame> frames;
	
	/**
	 * Private constructor
	 */
	private BackingStore() {
		logger = new Logger(this.getClass());
		frames = new ArrayList<>(256);
		try {
			File file = new File("resources/BACKING_STORE.bin");
			FileInputStream fis = new FileInputStream(file);
			backingBytes = IOUtils.toByteArray(fis);
			
			//create 256 frames, each of 256 bytes
			int counter = 0;
			byte[] oneFrame = new byte[256];
			for (byte b : backingBytes) {
				oneFrame[counter] = b;
				counter++;
				if (counter == 256) {
					//reset the counter & save the frame
					counter = 0;
					BackingFrame bf = new BackingFrame(oneFrame);
					frames.add(bf);
					oneFrame = new byte[256];
				}
				
			}
			
		} catch (IOException e) {
			logger.error("Failed to read the BACKING STORE",e);
		} catch (Exception e) {
			logger.error("Failed to create the backing frames",e);
		}
	}
	
	/**
	 * Singleton accessor
	 * @return BackingStore
	 */
	public static BackingStore getInstance() {
		if (instance == null) {
			instance = new BackingStore();
		}
		return instance;
	}
	
	
	/**
	 * Given the page and offset, 
	 * get the byte from the appropriate frame and 
	 * offset within that frame.
	 * 
	 * @param page int value (between 0 and 255
	 * @param offset int value (between 0 and 255
	 * @return byte - the value we want
	 */
	public byte getByteAt(int page, int offset) {
		BackingFrame bf = frames.get(page);
		return bf.getByteAtOffset(offset);
	}
	
	/**
	 * We've encounted a Read/Write bit that indicates
	 * that a value needs to be written to disk before
	 * being removed from the physical memory.
	 * 
	 * @param page int - 0 based
	 * @param offset int - 0 based
	 * @param valueToWrite byte - value to write
	 */
	public void writeByteTo(int page, int offset, byte valueToWrite) {
		BackingFrame bf = frames.get(page);
		bf.setByteAtOffset(offset, valueToWrite);
	}

}
